<?php
/**
 * crcl/base
 *
 * Copyright 2013 CRCL - André Sabosch - info@crcl.de
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace crcl\base\database\renderer;

interface IMysql
{
  public function execute();
}

class ContainerMysql extends Concrete implements IMysql
{
  public $aFrom;

  public function __construct()
  {
    $this->oFilterIdentifier = \crcl\base\Filter::mysqlidentifier();
  }

  public function execute()
  {
    parent::execute();

    switch (\get_class($this->oQuery))
    {
      case 'crcl\base\database\query\ContainerSelect':
        $this->renderStart();
        $this->renderType();
        $this->renderDistinct();
        $this->renderColumns();
        $this->renderTable();
        $this->renderJoin();
        $this->renderWhere();
        $this->renderGroupby();
        $this->renderOrderby();
        $this->renderLimit();
        $this->renderEnd();
        break;
      case 'crcl\base\database\query\ContainerInsert':
        $this->renderStart();
        $this->renderType();
        $this->renderTable();
        $this->renderData();
        $this->renderOnDuplicateKeyUpdate();
        $this->renderEnd();
        break;
      case 'crcl\base\database\query\ContainerUpdate':
        $this->renderStart();
        $this->renderType();
        $this->renderTable();
        $this->renderData();
        $this->renderWhere();
        $this->renderEnd();
        break;
      case 'crcl\base\database\query\ContainerDelete':
        $this->renderStart();
        $this->renderType();
        $this->renderTable();
        $this->renderWhere();
        $this->renderEnd();
        break;
      case 'crcl\base\database\query\ContainerReplace':
        $this->renderStart();
        $this->renderType();
        $this->renderTable();
        $this->renderData();
        $this->renderEnd();
        break;
    }

    return $this->result;
  }

  /** PRIVATE *************************************************************************************/

  private $oFilterIdentifier;

  private function renderStart()
  {
    $this->result = '';
  }

  private function renderEnd()
  {
    $this->result .= ';';
  }

  private function renderType()
  {
    switch (\get_class($this->oQuery))
    {
      case 'crcl\base\database\query\ContainerSelect':
        $this->result .= 'SELECT ';
        break;
      case 'crcl\base\database\query\ContainerInsert':
        $this->result .= 'INSERT ';
        break;
      case 'crcl\base\database\query\ContainerUpdate':
        $this->result .= 'UPDATE ';
        break;
      case 'crcl\base\database\query\ContainerDelete':
        $this->result .= 'DELETE ';
        break;
      case 'crcl\base\database\query\ContainerReplace':
        $this->result .= 'REPLACE ';
        break;
      default:
        throw new Exception('Invalid type.',
            Exception::ERROR_INVALIDTYPE, array('class' => \get_class($this->oQuery)));
    }
  }

  private function renderTable()
  {
    switch (\get_class($this->oQuery))
    {
      case 'crcl\base\database\query\ContainerSelect':
        $this->result .= 'FROM ';
        foreach ($this->oQuery->getTables() as $sTable)
        {
          $sTable = $this->checkIdentifier($sTable);
          $this->result .= $sTable;
        }
        break;
      case 'crcl\base\database\query\ContainerInsert':
      case 'crcl\base\database\query\ContainerReplace':
        $this->result .= 'INTO ';
        $this->result .= $this->checkIdentifier($this->oQuery->getTable());
        break;
      case 'crcl\base\database\query\ContainerUpdate':
        $this->result .= $this->checkIdentifier($this->oQuery->getTable());
        break;
      case 'crcl\base\database\query\ContainerDelete';
        $this->result .= 'FROM ';
        $this->result .= $this->checkIdentifier($this->oQuery->getTable());
        break;
    }
  }

  //select
  private function renderDistinct()
  {
    if ($this->oQuery->isDistinct())
    {
      $this->result .= 'DISTINCT ';
    }
  }

  private function renderColumns()
  {
    $aColumns = $this->oQuery->getColumns();
    if (empty($aColumns))
    {
      $this->result .= '* ';
    }
    else
    {
      $bFirst = \TRUE;
      foreach ($aColumns as $column)
      {
        if (!$bFirst)
        {
          $this->result .= ', ';
        }
        $this->result .= $column;
        if ($bFirst)
        {
          $bFirst = \FALSE;
        }
      }
      $this->result .= ' ';
    }
  }

  private function renderWhere()
  {
    $aWhere = $this->oQuery->getWhere();
    if (!empty($aWhere))
    {
      $this->result .= ' WHERE ';
      foreach ($aWhere as $aWherePart)
      {
        $sWhere = $aWherePart['where'];
        foreach ($aWherePart['values'] as $key => $value)
        {
          $sVar = ':' . $key;
          $value = \crcl\base\Filter::quote()->filter($value);
          $sWhere = \str_replace($sVar, $value, $sWhere);
        }
        if (!\is_null($aWherePart['operator']))
        {
          $sOperator = \strtoupper($aWherePart['operator']);
          $sWhere = ' ' . $sOperator . ' ' . $sWhere;
        }
        $this->result .= $sWhere;
      }
    }
  }

  private function renderGroupby()
  {
    $aGroupby = $this->oQuery->getGroupby();
    if (!empty($aGroupby))
    {
      $this->result .= ' GROUP BY ';
      $bFirst = \TRUE;
      foreach ($aGroupby as $aGroupbyPart)
      {
        if (!$bFirst)
        {
          $this->result .= ', ';
        }
        $bFirst = \FALSE;
        $this->result .= $aGroupbyPart['column'];
      }
    }
  }

  private function renderOrderby()
  {
    $aOrderby = $this->oQuery->getOrderby();
    if (!empty($aOrderby))
    {
      $this->result .= ' ORDER BY ';
      $bFirst = \TRUE;
      foreach ($aOrderby as $aOrderbyPart)
      {
        if (!$bFirst)
        {
          $this->result .= ', ';
        }
        $bFirst = \FALSE;
        $this->result .= $aOrderbyPart['column'];
        if (!\is_null($aOrderbyPart['direction']))
        {
          $sDirection = \strtoupper($aOrderbyPart['direction']);
          $this->result .= ' ' . $sDirection;
        }
      }
    }
  }

  private function renderLimit()
  {
    $iLimitCount = $this->oQuery->getLimit();
    if (!\is_null($iLimitCount))
    {
      $this->result .= ' LIMIT ';
      $this->result .= $iLimitCount;
    }
  }

  private function renderData()
  {
    $sColumns = '(';
    $sValues = '(';
    $bFirst = \TRUE;
    $bFirstValue = \TRUE;
    $aData = $this->oQuery->getData();

    if (\is_array($aData) && (!empty($aData)))
    {
      switch (\get_class($this->oQuery))
      {
        case 'crcl\base\database\query\ContainerInsert':
        case 'crcl\base\database\query\ContainerReplace':
          if (isset($aData[0]))
          {
            if (is_array($aData[0]))
            {
              foreach ($aData as $aLine)
              {
                $bFirst = \TRUE;
                $sValues = '(';
                foreach ($aLine as $col => $value)
                {
                  if (!$bFirst)
                  {
                    $sColumns .= ', ';
                    $sValues .= ', ';
                  }
                  else
                  {
                    $bFirst = \FALSE;
                  }
                  $sColumns .= $this->checkIdentifier($col);
                  $sValues .= \crcl\base\Database::mysql()->quote($value);
                }
                $sColumns .= ')';
                $sValues .= ')';

                if ($bFirstValue)
                {
                  $this->result .= ' ' . $sColumns . ' VALUES ' . $sValues;
                }
                else
                {
                  $this->result .= ', ' . $sValues;
                }
                $bFirstValue = \FALSE;
              }
              break;
            }
          }
          foreach ($aData as $col => $value)
          {
            if (!$bFirst)
            {
              $sColumns .= ', ';
              $sValues .= ', ';
            }
            else
            {
              $bFirst = \FALSE;
            }
            $sColumns .= $this->checkIdentifier($col);
            $sValues .= \crcl\base\Database::mysql()->quote($value);
          }
          $sColumns .= ')';
          $sValues .= ')';

          $this->result .= ' ' . $sColumns . ' VALUES ' . $sValues;
          break;
        case 'crcl\base\database\query\ContainerUpdate':
          $this->result .= ' SET ';
          foreach ($aData as $col => $value)
          {
            if (!$bFirst)
            {
              $this->result .= ', ';
            }
            else
            {
              $bFirst = \FALSE;
            }

            $this->result .= $this->checkIdentifier($col);
            $this->result .= ' = ';
            $this->result .= \crcl\base\Database::mysql()->quote($value);
          }
          break;
      }
    }
  }

  private function renderOnDuplicateKeyUpdate()
  {
    $aData = $this->oQuery->getOnDuplicateKeyUpdate();
    if (!empty($aData))
    {
      $this->result .= ' ON DUPLICATE KEY UPDATE ';

      $bFirst = \TRUE;
      foreach ($aData as $col => $value)
      {
        if (!$bFirst)
        {
          $this->result .= ', ';
        }
        else
        {
          $bFirst = \FALSE;
        }

        $this->result .= $this->checkIdentifier($col);
        $this->result .= ' = ';
        $this->result .= \crcl\base\Database::mysql()->quote($value);
      }
    }
  }

  private function renderJoin()
  {
    $aJoin = $this->oQuery->getJoin();
    if (!empty($aJoin))
    {
      $sType = \strtoupper($aJoin['type']);
      $this->result .= ' ' . $sType . ' JOIN ';
      $this->result .= $aJoin['table'];
      $sOn = ' ON (' . $aJoin['condition'] . ')';
      $this->result .= $sOn;
    }
  }

  private function checkIdentifier($sIdentifier)
  {
    $oValidatorIdentifier = \crcl\base\Validator::mysqlidentifier();

    if (!$oValidatorIdentifier->is($sIdentifier))
    {
      $sIdentifier = $this->oFilterIdentifier->filter($sIdentifier);
    }
    $oValidatorIdentifier->test($sIdentifier);

    return $sIdentifier;
  }
}